﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.IO;

using Gurucore.Framework.Core.Util;
using Gurucore.Framework.Core.JITGeneration;

using Ader.TemplateEngine;

namespace Gurucore.Framework.Core.Proxy
{
	public class ProxyGenerator
	{
		private class InterceptorInfo
		{
			private string m_sAssembly;

			public string Assembly
			{
				get { return m_sAssembly; }
				set { m_sAssembly = value; }
			}

			private string m_sClass;

			public string Class
			{
				get { return m_sClass; }
				set { m_sClass = value; }
			}
		}

		private class PropertyDeclaration
		{
			private string m_sName;

			public string Name
			{
				get { return m_sName; }
				set { m_sName = value; }
			}

			private string m_sPropertyType;

			public string PropertyType
			{
				get { return m_sPropertyType; }
				set { m_sPropertyType = value; }
			}

			private List<InterceptorInfo> m_arrSetterBeforeCallingInterceptors = new List<InterceptorInfo>();
			private List<InterceptorInfo> m_arrSetterAfterCallSuccessInterceptors = new List<InterceptorInfo>();
			private List<InterceptorInfo> m_arrSetterAfterCallFailureInterceptors = new List<InterceptorInfo>();
			private List<InterceptorInfo> m_arrSetterAfterReturnedInterceptors = new List<InterceptorInfo>();

			public void AddSetterInterceptor(string p_sAssembly, string p_sClass, InterceptorType p_eType)
			{
				InterceptorInfo oInterceptor = new InterceptorInfo();
				oInterceptor.Assembly = p_sAssembly;
				oInterceptor.Class = p_sClass;

				switch (p_eType)
				{
					case InterceptorType.BeforeCalling:
						m_arrSetterBeforeCallingInterceptors.Add(oInterceptor);
						break;
					case InterceptorType.AfterCallSuccess:
						m_arrSetterAfterCallSuccessInterceptors.Add(oInterceptor);
						break;
					case InterceptorType.AfterCallFailure:
						m_arrSetterAfterCallFailureInterceptors.Add(oInterceptor);
						break;
					case InterceptorType.AfterReturned:
						m_arrSetterAfterReturnedInterceptors.Add(oInterceptor);
						break;
				}
			}

			public InterceptorInfo[] SetterBeforeCallingInterceptors
			{
				get
				{
					return m_arrSetterBeforeCallingInterceptors.ToArray();
				}
			}

			public InterceptorInfo[] SetterAfterCallSuccessInterceptors
			{
				get
				{
					return m_arrSetterAfterCallSuccessInterceptors.ToArray();
				}
			}

			public InterceptorInfo[] SetterAfterCallFailureInterceptors
			{
				get
				{
					return m_arrSetterAfterCallFailureInterceptors.ToArray();
				}
			}

			public InterceptorInfo[] SetterAfterReturnedInterceptors
			{
				get
				{
					return m_arrSetterAfterReturnedInterceptors.ToArray();
				}
			}

			public bool SetterIntercepted
			{
				get
				{
					return (m_arrSetterBeforeCallingInterceptors.Count > 0)
						|| (m_arrSetterAfterCallSuccessInterceptors.Count > 0)
						|| (m_arrSetterAfterCallFailureInterceptors.Count > 0)
						|| (m_arrSetterAfterReturnedInterceptors.Count > 0);
				}
			}

			private List<InterceptorInfo> m_arrGetterBeforeCallingInterceptors = new List<InterceptorInfo>();
			private List<InterceptorInfo> m_arrGetterAfterCallSuccessInterceptors = new List<InterceptorInfo>();
			private List<InterceptorInfo> m_arrGetterAfterCallFailureInterceptors = new List<InterceptorInfo>();
			private List<InterceptorInfo> m_arrGetterAfterReturnedInterceptors = new List<InterceptorInfo>();

			public void AddGetterInterceptor(string p_sAssembly, string p_sClass, InterceptorType p_eType)
			{
				InterceptorInfo oInterceptor = new InterceptorInfo();
				oInterceptor.Assembly = p_sAssembly;
				oInterceptor.Class = p_sClass;

				switch (p_eType)
				{
					case InterceptorType.BeforeCalling:
						m_arrGetterBeforeCallingInterceptors.Add(oInterceptor);
						break;
					case InterceptorType.AfterCallSuccess:
						m_arrGetterAfterCallSuccessInterceptors.Add(oInterceptor);
						break;
					case InterceptorType.AfterCallFailure:
						m_arrGetterAfterCallFailureInterceptors.Add(oInterceptor);
						break;
					case InterceptorType.AfterReturned:
						m_arrGetterAfterReturnedInterceptors.Add(oInterceptor);
						break;
				}
			}

			public InterceptorInfo[] GetterBeforeCallingInterceptors
			{
				get
				{
					return m_arrGetterBeforeCallingInterceptors.ToArray();
				}
			}

			public InterceptorInfo[] GetterAfterCallSuccessInterceptors
			{
				get
				{
					return m_arrGetterAfterCallSuccessInterceptors.ToArray();
				}
			}

			public InterceptorInfo[] GetterAfterCallFailureInterceptors
			{
				get
				{
					return m_arrGetterAfterCallFailureInterceptors.ToArray();
				}
			}

			public InterceptorInfo[] GetterAfterReturnedInterceptors
			{
				get
				{
					return m_arrGetterAfterReturnedInterceptors.ToArray();
				}
			}
			
			public bool GetterIntercepted
			{
				get
				{
					return (m_arrGetterBeforeCallingInterceptors.Count > 0)
						|| (m_arrGetterAfterCallSuccessInterceptors.Count > 0)
						|| (m_arrGetterAfterCallFailureInterceptors.Count > 0)
						|| (m_arrGetterAfterReturnedInterceptors.Count > 0);
				}
			}
		}

		private class MethodDeclaration
		{
			private string m_sName;

			public string Name
			{
				get { return m_sName; }
				set { m_sName = value; }
			}

			private string m_sParamsDecl;

			public string ParamsDecl
			{
				get { return m_sParamsDecl; }
				set { m_sParamsDecl = value; }
			}

			private string m_sParams;

			public string Params
			{
				get { return m_sParams; }
				set { m_sParams = value; }
			}

			private string m_sReturnType;

			public string ReturnType
			{
				get { return m_sReturnType; }
				set { m_sReturnType = value; }
			}

			private bool m_bIsVoidMethod;

			public bool IsVoidMethod
			{
				get { return m_bIsVoidMethod; }
				set { m_bIsVoidMethod = value; }
			}

			public bool IsWrapped
			{
				get { return m_oCallWrapperInterceptor != null; }
			}

			private List<InterceptorInfo> m_arrBeforeCallingInterceptors = new List<InterceptorInfo>();
			private List<InterceptorInfo> m_arrAfterCallSuccessInterceptors = new List<InterceptorInfo>();
			private List<InterceptorInfo> m_arrAfterCallFailureInterceptors = new List<InterceptorInfo>();
			private List<InterceptorInfo> m_arrAfterReturnedInterceptors = new List<InterceptorInfo>();
			private InterceptorInfo m_oCallWrapperInterceptor;

			public void AddInterceptor(string p_sAssembly, string p_sClass, InterceptorType p_eType)
			{
				InterceptorInfo oInterceptor = new InterceptorInfo();
				oInterceptor.Assembly = p_sAssembly;
				oInterceptor.Class = p_sClass;

				switch (p_eType)
				{
					case InterceptorType.BeforeCalling:
						m_arrBeforeCallingInterceptors.Add(oInterceptor);
						break;
					case InterceptorType.AfterCallSuccess:
						m_arrAfterCallSuccessInterceptors.Add(oInterceptor);
						break;
					case InterceptorType.AfterCallFailure:
						m_arrAfterCallFailureInterceptors.Add(oInterceptor);
						break;
					case InterceptorType.AfterReturned:
						m_arrAfterReturnedInterceptors.Add(oInterceptor);
						break;
					case InterceptorType.CallWrapper:
						m_oCallWrapperInterceptor = oInterceptor;
						break;
				}
			}

			public InterceptorInfo[] BeforeCallingInterceptors
			{
				get
				{
					return m_arrBeforeCallingInterceptors.ToArray();
				}
			}

			public InterceptorInfo[] AfterCallSuccessInterceptors
			{
				get
				{
					return m_arrAfterCallSuccessInterceptors.ToArray();
				}
			}

			public InterceptorInfo[] AfterCallFailureInterceptors
			{
				get
				{
					return m_arrAfterCallFailureInterceptors.ToArray();
				}
			}

			public InterceptorInfo[] AfterReturnedInterceptors
			{
				get
				{
					return m_arrAfterReturnedInterceptors.ToArray();
				}
			}

			public string CallWrapperClass
			{
				get
				{
					return m_oCallWrapperInterceptor.Class;
				}
			}

			public string CallWrapperAssembly
			{
				get
				{
					return m_oCallWrapperInterceptor.Assembly;
				}
			}
		}

		private Type m_oBaseClass;
		private List<MethodDeclaration> m_arrConstructors;
		private Dictionary<MethodInfo, MethodDeclaration> m_dicProxiedMethods;
		private Dictionary<PropertyInfo, PropertyDeclaration> m_dicProxiedProperties;
		private string m_sTemplate;

		public ProxyGenerator(Type p_oBaseClass)
		{
			m_oBaseClass = p_oBaseClass;
			ConstructorInfo[] arrConstructors = m_oBaseClass.GetConstructors();
			m_arrConstructors = new List<MethodDeclaration>();
			foreach (ConstructorInfo oConstructor in arrConstructors)
			{
				m_arrConstructors.Add(GetConstructorDeclaration(oConstructor));
			}

			m_dicProxiedMethods = new Dictionary<MethodInfo, MethodDeclaration>();
			m_dicProxiedProperties = new Dictionary<PropertyInfo, PropertyDeclaration>();
		}

		public void SetTemplate(string p_sTemplate)
		{
			m_sTemplate = p_sTemplate;
		}

		public void AddMethodInterceptor(MethodInfo p_oMethod, string p_sInterceptorAssembly, string p_sInterceptorClass, InterceptorType p_eType)
		{
			if (!(p_oMethod.IsVirtual || p_oMethod.IsAbstract))
			{
				return;
			}

			MethodDeclaration oMethodDecl = null;
			if (m_dicProxiedMethods.ContainsKey(p_oMethod))
			{
				oMethodDecl = m_dicProxiedMethods[p_oMethod];
			}
			else
			{
				oMethodDecl = GetMethodDeclaration(p_oMethod);
				m_dicProxiedMethods.Add(p_oMethod, oMethodDecl);
			}
			oMethodDecl.AddInterceptor(p_sInterceptorAssembly, p_sInterceptorClass, p_eType);
		}

		public void AddSetterInterceptor(PropertyInfo p_oProperty, string p_sInterceptorAssembly, string p_sInterceptorClass, InterceptorType p_eType)
		{
			//check virtual or abstract
			PropertyDeclaration oPropDecl = null;
			if (m_dicProxiedProperties.ContainsKey(p_oProperty))
			{
				oPropDecl = m_dicProxiedProperties[p_oProperty];
			}
			else
			{
				oPropDecl = GetPropertyDeclaration(p_oProperty);
				m_dicProxiedProperties.Add(p_oProperty, oPropDecl);
			}
			oPropDecl.AddSetterInterceptor(p_sInterceptorAssembly, p_sInterceptorClass, p_eType);
		}

		public void AddGetterInterceptor(PropertyInfo p_oProperty, string p_sInterceptorAssembly, string p_sInterceptorClass, InterceptorType p_eType)
		{
			PropertyDeclaration oPropDecl = null;
			if (m_dicProxiedProperties.ContainsKey(p_oProperty))
			{
				oPropDecl = m_dicProxiedProperties[p_oProperty];
			}
			else
			{
				oPropDecl = GetPropertyDeclaration(p_oProperty);
				m_dicProxiedProperties.Add(p_oProperty, oPropDecl);
			}
			oPropDecl.AddGetterInterceptor(p_sInterceptorAssembly, p_sInterceptorClass, p_eType);
		}

		private string Generate()
		{
			if (m_sTemplate.NullOrEmpty())
			{
				TextReader oReader = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream("Gurucore.Framework.Core.Proxy.ProxyTemplate.tpl"));
				m_sTemplate = oReader.ReadToEnd();
				oReader.Close();
			}

			TemplateManager oTplMgr = TemplateManager.FromString(m_sTemplate);
			oTplMgr.SetValue("class", m_oBaseClass);
			oTplMgr.SetValue("constructors", m_arrConstructors.ToArray());
			oTplMgr.SetValue("methods", m_dicProxiedMethods.Values.ToArray());
			oTplMgr.SetValue("properties", m_dicProxiedProperties.Values.ToArray());

			string sGeneratedCode = oTplMgr.Process();
			System.Diagnostics.Debug.Write(sGeneratedCode);

			return sGeneratedCode;
		}

		public Type GetProxyClass()
		{
			string sCode = string.Empty;

			JITClassManager oJITClassManager = Application.GetInstance().GetGlobalSharedObject<JITClassManager>();
			string sClassName = m_oBaseClass.FullName + "_Proxy";

			if (!oJITClassManager.IsRegistered(sClassName))
			{
				sCode = Generate();
			}
			Type oGeneratedClass = oJITClassManager.GetRegisteredClass(sClassName, sCode, true);
			return oGeneratedClass;
		}

		public bool IsGenerated()
		{
			JITClassManager oJITClassManager = Application.GetInstance().GetGlobalSharedObject<JITClassManager>();
			string sClassName = m_oBaseClass.FullName + "_Proxy";

			return oJITClassManager.IsRegistered(sClassName);
		}

		private PropertyDeclaration GetPropertyDeclaration(PropertyInfo p_oProperty)
		{
			PropertyDeclaration oPropDecl = new PropertyDeclaration();
			oPropDecl.Name = p_oProperty.Name;
			oPropDecl.PropertyType = p_oProperty.PropertyType.Name;
			return oPropDecl;
		}

		private MethodDeclaration GetMethodDeclaration(MethodInfo p_oMethod)
		{
			MethodDeclaration oMethodDecl = new MethodDeclaration();
			oMethodDecl.Name = p_oMethod.Name;
			oMethodDecl.ReturnType = p_oMethod.ReturnType.Name;
			oMethodDecl.IsVoidMethod = (p_oMethod.ReturnType == typeof(void));

			ParameterInfo[] arrParams = p_oMethod.GetParameters();
			string sParamsDecl = string.Empty;
			string sParams = string.Empty;

			foreach (ParameterInfo oParam in arrParams)
			{
				sParamsDecl += oParam.ParameterType.Name + " " + oParam.Name + ", ";
				sParams += oParam.Name + ", ";
			}

			if (sParamsDecl.Length > 0)
			{
				oMethodDecl.ParamsDecl = sParamsDecl.Substring(0, sParamsDecl.Length - 2);
			}
			else
			{
				oMethodDecl.ParamsDecl = sParamsDecl;
			}

			if (sParams.Length > 0)
			{
				oMethodDecl.Params = sParams.Substring(0, sParams.Length - 2);
			}
			else
			{
				oMethodDecl.Params = sParams;
			}

			return oMethodDecl;
		}

		private MethodDeclaration GetConstructorDeclaration(ConstructorInfo p_oMethod)
		{
			MethodDeclaration oMethodDecl = new MethodDeclaration();
			oMethodDecl.Name = p_oMethod.DeclaringType.Name;;

			ParameterInfo[] arrParams = p_oMethod.GetParameters();
			string sParamsDecl = string.Empty;
			string sParams = string.Empty;

			foreach (ParameterInfo oParam in arrParams)
			{
				sParamsDecl += oParam.ParameterType.Name + " " + oParam.Name + ", ";
				sParams += oParam.Name + ", ";
			}

			if (sParamsDecl.Length > 0)
			{
				oMethodDecl.ParamsDecl = sParamsDecl.Substring(0, sParamsDecl.Length - 2);
			}
			else
			{
				oMethodDecl.ParamsDecl = sParamsDecl;
			}

			if (sParams.Length > 0)
			{
				oMethodDecl.Params = sParams.Substring(0, sParams.Length - 2);
			}
			else
			{
				oMethodDecl.Params = sParams;
			}

			return oMethodDecl;
		}
	}
}
